# coding:utf-8

"""
CopyRight(c)  2017-09-15  Fraplus Corp

Author: Weilin Shen, Xiaodong.yang
Email:  xiaodong.yang@unidt.com
Functions:

we chat server

dependence package:

fralog
handler

"""
from cache.cache import *
from handler.freshhandler import FreshHandler
import requests
import json
import os.path
import datetime
import sys
import string
import time
#
# 线程安全的队列
#
import queue
import threading

import pymongo
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.web import RequestHandler

from scipy.stats import norm

import UploadSurvey as us
from fralog.FraLog import Fralog
from wechat.wechat import WeChatHandler, WeChatHandlerNoJewellery

from handler.FraHandler import FraHandler
from handler.signalhandler import SigProccess
from handler.bresultshandler import BResultHandler
from scipy.stats import norm

host = '120.132.101.30'

log = Fralog().getFileLog()

# #
# # 本地缓存
# #
#
# # 问卷的缓存
# survey_code = {}
#
# #
# # 答题规则缓存
# #
# select_survey = {}
#
# #
# # 写库任务队列
# #
# write_queue = queue.Queue()
# write_queue_response_model = queue.Queue()
#
# #
# # 报告模板
# #
# desc_map = {}

#
# TODO: 不要将逻辑上为一个整理的数据分散到两个数据库，这样会引发很多不便，埋下难以发觉的BUG
#
db, client = us.get_db_client('survey_database')
db2, client2 = us.get_db_client('survey_character')

db_connect = {}
db_connect['v28'] = db
db_connect['hr'] = db2
db_connect['canfu2'] = db


class ProductScore(FraHandler):
    """
    商品评分
    """

    def get(self, *args, **kwargs):
        pass

    def post(self, *args, **kwargs):
        log.info('保存商品评分信息')
        prods = self.request.body
        log.info('评分信息:%s' % (prods))
        prods = json.loads(prods.decode('utf-8'))
        db, client = us.get_db_client()
        db.products_collection.insert(prods)


class SurveyListHandler(FraHandler):
    """
    系统可用的问卷列表
    """

    def get(self, fname):
        try:
            txt = open(fname).read()
            self.write(txt)
        except Exception:
            self.write('not found: %s' % (fname))


class SurveyHandler(FraHandler):
    """
    返回问卷所包含的题目
    """

    def get(self, channel, wechat):
        global survey_code, db_connect
        """
        申请问卷，需提供渠道号和用户的微信号
        :param channel: 
        :param wechat: 微信的openid
        :return: 
        """
        code = None
        try:
            code = self.get_argument('survey')
        except Exception as err:
            print('get code request error:', err)

        #
        # 缓存命中，此时可直接返回结果，省去读取数据库的开销
        #
        if survey_code.get(code) is not None:
            self.write(survey_code.get(code))
            return
        log.info('缓存未命中，回源查找数据.........')
        # log.info('Get Survey List')
        # if code == 'hr':
        #     if db_connect.get(code) is None:
        #         db, client = us.get_db_client('survey_character')
        #         db_connect[code] = db
        #     db = db_connect[code]
        # else:
        #     if db_connect.get(code) is None:
        #         db, client = us.get_db_client('survey_database')
        #         db_connect[code] = db
        #     db = db_connect[code]
        db = get_db_by_code(code)
        # print('into code:',code)
        selected_survey = db.surveys_collection.find_one(
            {"code": self.get_argument('survey')})  # 通过 code 在 surveys_collection 中取得此问卷的信息
        if selected_survey is None:
            self.write('get selected_survey is NULL')
            return
        question_ids = selected_survey['questions']  # 问卷的题目存在 questions 字段里，题目以逗号分隔符存储，如，1,2,3,4,5
        # print('ids:',question_ids)
        question_ids = question_ids.split(',')  # 转为 python list
        infos = {}
        infos['code'] = selected_survey['code']  # 问卷编码
        infos['description'] = selected_survey['description']  # 问卷介绍
        infos['channel'] = channel
        infos['wechat'] = wechat

        questions = []

        count = 0
        for question_id in question_ids:  # 每一个问题
            each = db.questions_collection.find_one({"question_id": int(question_id)})  # int(question_id) 数据类型一定要一样！
            info = {}
            info['order_id'] = count + 1
            info['question_id'] = question_id
            info['type'] = each['type']
            info['required'] = each['required']
            info['title'] = each['title']
            options = []
            selects = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
            for sel in selects:
                if each[sel] is not None:
                    options.append(each[sel])
            info['options'] = options
            questions.append(info)
            count += 1
            # print('get info:',info)

        infos['questions'] = questions

        infos = json.dumps(infos)
        #
        # 缓存未命中，添加缓存
        #
        survey_code[code] = infos
        self.write(infos)
        # self.render('survey.html', infos = infos)


def get_db_by_code(code):
    global db, db2
    if db_connect.get(code) is None:
        db_connect[code] = db
    return db_connect.get(code)


def get_code_type(code):
    db = get_db_by_code(code)
    type = db.codetype.find_one({'code': code})
    if type is None:
        return 'standard', 'standard'
    else:
        return type['survey_type'], type['report_code']


def get_desc_by_code(code, dim):
    db = get_db_by_code(code)
    #
    # 根据 code 获取报告类型
    codetype = db.codetype.find_one({"code": code})
    #
    # 如果在该库中没有对应的code，则该问卷为老版本问卷，直接使用原始的报告模板即可
    #
    if codetype is None:
        src_collection = 'desc_collection'
        desc = db[src_collection].find_one({"id": dim})
    else:
        src_collection = codetype['src_collection']
        rptcode = codetype['report_code']
        desc = db[src_collection].find_one({"id": dim, "code": rptcode})
    return desc


class GetResponseModelHandler(FraHandler):
    """
    获得ResponseModel题目,普通问答后的特征是否准确题目
    """

    def get(self, *args, **kwargs):
        pass

    def post(self, *args, **kwargs):
        # log.info('获得ResponseModel题目,普通问答后的特征是否准确题目')
        self.set_header('content-type', 'json')
        #
        # 修改获取请求数据的方式
        params = self.request.body
        # log.info(params)
        data = json.loads(params.decode('utf-8'))
        # code = data['code']
        # channel = data['channel']
        # wxid = data['wxid']
        big_five = {'O': data['O'], 'C': data['C'], 'E': data['E'], 'A': data['A'], 'N': data['N']}
        ret = get_question(big_five)
        for item in ret:
            item['options'] = ['神准', '一般', '不准']
        self.write(json.dumps(ret))


class BackResponseModelHandler(FraHandler):
    """
    提交ResponseModel题目,普通问答后的特征是否准确题目
    """

    def get(self, *args, **kwargs):
        pass

    def post(self, *args, **kwargs):
        # log.info('提交ResponseModel题目,普通问答后的特征是否准确题目')
        self.set_header('content-type', 'json')
        #
        # 修改获取请求数据的方式
        params = self.request.body
        # log.info(params)
        data = json.loads(params.decode('utf-8'))
        # code = data['code']
        # channel = data['channel']
        # wxid = data['wxid']
        log.info('写model库')
        write_queue_response_model.put(data)
        self.write(data)


class GetPicUrlHandler(FraHandler):
    """
    pic url对象关系
    """

    def get(self, *args, **kwargs):
        self.post(self, args, kwargs)

    def post(self, *args, **kwargs):
        # log.info('提交ResponseModel题目,普通问答后的特征是否准确题目')
        self.set_header('content-type', 'json')
        #
        # 修改获取请求数据的方式
        params = self.request.body
        # log.info(params)
        data = []
        for item in db.pic_url_collection.find():
            del item['_id']
            data.append(item)
        # print(data)
        # data = json.loads(params.decode('utf-8'))
        # code = data['code']
        # channel = data['channel']
        # wxid = data['wxid']
        # write_queue_response_model.put(data)
        self.write(json.dumps(data))


class GetBXJJRShortlongurlHandler(FraHandler):
    """
    short long url 判断是否是保险经纪人，若是则判断是否在有效期内
       若在有效期，返回长连接，若不在有效期，直接返回提示过期
    """

    def get(self, *args, **kwargs):
        self.post(self, args, kwargs)

    def post(self, *args, **kwargs):
        self.set_header('content-type', 'json')
        post_data = {}
        rst = []
        for key in self.request.arguments:
            post_data[key] = self.get_arguments(key)
            shorturl = post_data['shorturl'][0]
            user_id = post_data['user_id'][0]
            user_limit = db.t_user_limit.find_one({"user_id": user_id, "code": shorturl[1:]})
            dead_line = user_limit["dead_line"][0]
            now_date = datetime.date.today()
            if now_date > dead_line:
                rst.append({'error': 1})
            else:
                # 判断是否已经到期 获取有效期限
                condition = {"shorturl": shorturl}
                for item in db.short_longurl_collection.find(condition):
                    del item['_id']
                    rst.append(item)
                    break
        self.write(json.dumps(rst))


class GetShortlongurlHandler(FraHandler):
    """
    short long url对象关系
    """

    def get(self, *args, **kwargs):
        self.post(self, args, kwargs)

    def post(self, *args, **kwargs):
        self.set_header('content-type', 'json')
        post_data = {}
        for key in self.request.arguments:
            post_data[key] = self.get_arguments(key)
        # log.info(params)
        shorturl = post_data['shorturl'][0]
        rst = []
        condition = {"shorturl": shorturl}
        for item in db.short_longurl_collection.find(condition):
            del item['_id']
            rst.append(item)
            break
        # print(data)
        # data = json.loads(params.decode('utf-8'))
        # code = data['code']
        # channel = data['channel']
        # wxid = data['wxid']
        # write_queue_response_model.put(data)
        self.write(json.dumps(rst))


class ResultHandler(FraHandler):
    """
    查看报告
    用户提交答题数据，进入该handler计算分数以及生成报告
    注意： 该handler处理标准问卷的算分
    """

    def post(self):
        global select_survey, db_connect
        log.info('收到用户提交的答案，准备分析......')
        # log.info('recv result request')
        self.set_header('content-type', 'json')
        #
        # 修改获取请求数据的方式
        params = self.request.body
        # log.info(params)
        data = json.loads(params.decode('utf-8'))
        code = data['code']

        #
        # 新的code， 不特殊指定的话，按照标准问卷来处理
        #
        type, report_code = get_code_type(code)
        params = {}

        if report_code is not None:
            if type == 'standard':
                log.info('使用标准算法计算OCEAN')
                ret = self._process_standard_data(code, data)
            else:
                log.info('计算特征题库OCEAN')
                ret = self._process_character_data(code, data)
            self.write(json.dumps(ret))
        else:
            #
            # 数据库中无该条记录时，为老版本的V28题，为考虑兼容性而设置，统一使用标准报告
            #
            if type == 'standard':
                self._process_standard_data(code, data)
            else:
                self._process_character_data(code, data)
            self.write('0')
            log.info('不需要返回报告，已发送0')
            #
            # if code == 'v28' or code == 'canfu2' :
            #     ret = self._process_standard_data(code, data)
            # elif code == 'hr':
            #     ret = self._process_character_data(code,data)
            # else:
            #     ret = self._process_standard_data(code, data)
            #  例：{'_id': ObjectId('596c39a7383d240f494917b7'), 'code': 'coffee',
            #  'response': {'q1': 'C', 'q2': 'B', 'q3': 'D', 'q4': 'D', 'q5': 'E', 'q6': 'A',
            #  'q7': 'C', 'q8': 'B', 'q9': 'B', 'q10': 'D'},
            # 'bigFive': {'O': 2.0, 'C': 4.0, 'E': 4.0, 'A': 3.5, 'N': 3.5},
            #  'norm': {'O_norm_mean': 3.25, 'C_norm_mean': 3.35, 'E_norm_mean': 3.23,
            #  'A_norm_mean': 3.64, 'N_norm_mean': 2.81}}
            # TODO:保存用户更多信息，如：IP 地址，访问设备，测试时间及测试用时。

    def cum(self, x):
        """
        cum 与 score 函数用于 将 大五归一化后的 beta 值 转换为 1-5 的得分（by 徐清）
        """
        t = float(x)
        s = 0.5 + (3 * t - t ** 3) / 4
        return s

    def score(self, x, mean=3, sd=1, mode='b'):
        """
        归一后的 beta 值 与 1-5分值 之间的互相转换
        """
        if mode == 'b':
            percent = self.cum(x)
            a = norm.ppf(percent)
        else:
            a = x
        ori = mean + a * sd
        if ori > 5:
            ori = 5
        if ori < 1:
            ori = 1
        return ori

    def normalization(self, O, C, E, A, N):
        """
        值的归一化
        """
        bO = O / ((O ** 2 + C ** 2 + E ** 2 + A ** 2 + N ** 2) ** 0.5)
        bC = C / ((O ** 2 + C ** 2 + E ** 2 + A ** 2 + N ** 2) ** 0.5)
        bE = E / ((O ** 2 + C ** 2 + E ** 2 + A ** 2 + N ** 2) ** 0.5)
        bA = A / ((O ** 2 + C ** 2 + E ** 2 + A ** 2 + N ** 2) ** 0.5)
        bN = N / ((O ** 2 + C ** 2 + E ** 2 + A ** 2 + N ** 2) ** 0.5)

        return bO, bC, bE, bA, bN

    def give_score_detail(self, raw, norm_mean, norm_sd, dim='o', code='v28', desc=''):
        global db_connect, desc_map

        detail = ''
        title = ''
        desc = desc_map[code][dim]
        if desc is None:
            return ''
        if (raw < norm_mean - norm_sd):  # 得分低于常模减去一个标准差，那么这个得分 '低于平均水平'
            detail = desc['l_detail']
            title = desc['l_title']
            direct = -1
        elif (raw > norm_mean + norm_sd):
            title = desc['h_title']
            detail = desc['h_detail']
            direct = 1
        else:
            detail = desc.get('mean')
            title = '四面八方无死角'
            direct = 0
        return {'detail': detail, 'title': title, 'direct': direct}

    def calculateBigFiveScore(self, scoring, data=None):
        score = 0.0
        score_item = scoring
        try:
            for each in score_item:
                if "R" not in each:  # "R" 此题为反向计分题，不包含 "R" 为正向计分题
                    resp = data[each]  # self.get_argument('q' + str(index))
                    # log.debug('%s' % (resp))
                    response = resp['answer']
                    if (response == 'A'):  # 选 A 得1分
                        response = 1
                    elif (response == 'B'):
                        response = 2
                    elif (response == 'C'):
                        response = 3
                    elif (response == 'D'):
                        response = 4
                    elif (response == 'E'):
                        response = 5
                    else:
                        response = 0
                else:  # 反向计分题
                    each = each.replace("R", "")
                    resp = data[each]  # self.get_argument('q' + str(abs(index)))
                    response = resp['answer']
                    if (response == 'A'):  # 选 A 得5分
                        response = 5
                    elif (response == 'B'):
                        response = 4
                    elif (response == 'C'):
                        response = 3
                    elif (response == 'D'):
                        response = 2
                    elif (response == 'E'):
                        response = 1
                    else:
                        response = 0
                score += response
            return score / len(scoring)  # 得分 介于 1-5
        except Exception as e:
            return 0

    def _is_question_required(self, db, question_id):
        '''
        判断该题是否用于计算大五
        :param db: 
        :param question_id: 
        :return: 
        '''
        row = db.questions_collection.find_one({'question_id': question_id})
        pass

    def calculateBigFiveScore2(self, code='hr', data=None):
        db = get_db_by_code(code)
        questions = db.surveys_collection.find_one({'code': code})['questions'].split(',')
        all_beta = []
        try:
            for key in questions:  # each 是题目编号
                mk = int(key)
                question = db.questions_collection.find_one(
                    {"question_id": mk})  # 从数据表里根据 question_id 取出 "beta_O	beta_C	beta_E	beta_A	beta_N	是否人格题"
                if question is None:
                    continue
                # 如果题目类型0 1 2 0标准 1特征 2自定义 是自定义则无需计算
                if question['question_type'] is None or question['question_type'] == 2:
                    continue
                resp = data[key]  # self.get_argument('q' + str(index))
                # log.debug('%s' % (resp))
                response = resp['answer']
                if (response == 'A'):  # 选 A 得1分
                    response = 1
                elif (response == 'B'):
                    response = 2
                elif (response == 'C'):
                    response = 3
                elif (response == 'D'):
                    response = 4
                elif (response == 'E'):
                    response = 5
                else:
                    response = 0
                if question['beta_O'] + question['beta_C'] + question['beta_E'] + question['beta_A'] + question[
                    'beta_N'] != '':  # beta 有值得时候才是有用的测试问题（性别年龄不是）
                    beta = [response, question['beta_O'], question['beta_C'], question['beta_E'], question['beta_A'],
                            question['beta_N'], question['question_type']]  # [选项，此题大五 beta 值，此题 是否人格题]
                    all_beta.append(beta)

            bO, bC, bE, bA, bN = 0.0, 0.0, 0.0, 0.0, 0.0
            for item in all_beta:
                print(item)
                if item[6] == 1:
                    factor = 2.0  # 如果是人格题，加重权重，如 factor = 2.0
                else:
                    factor = 1.0

                item[0] = item[0] - 3
                bO += item[0] * item[1] * factor  # response * question['beta_O'] * factor
                bC += item[0] * item[2] * factor
                bE += item[0] * item[3] * factor
                bA += item[0] * item[4] * factor
                bN += item[0] * item[5] * factor
            print('%f %f %f %f %f' % (bO, bC, bE, bA, bN))
            print('大五得分(归一化的 beta值)：', self.normalization(bO, bC, bE, bA, bN))
            return self.normalization(bO, bC, bE, bA, bN)  # 归一化的值

        except Exception as e:
            print(e)
            return 0

    def _process_standard_data(self, code, data):
        #
        # 判断缓存是否命中
        #

        global select_survey, db_connect

        db = get_db_by_code(code)
        # if code == 'hr':
        #     if db_connect.get(code) is None:
        #         db, client = us.get_db_client('survey_character')
        #         db_connect[code] = db
        #     db = db_connect[code]
        # else:
        #     if db_connect.get(code) is None:
        #         db, client = us.get_db_client('survey_database')
        #         db_connect[code] =db
        #     db = db_connect[code]

        if select_survey.get(code) is None:
            selected_survey = db.surveys_collection.find_one({"code": code})
            select_survey[code] = selected_survey
        else:
            selected_survey = select_survey[code]

        O_raw, C_raw, E_raw, A_raw, N_raw = 0.0, 0.0, 0.0, 0.0, 0.0
        # 计算大五人格各个维度的得分
        '''
        大五得分分为 O_raw 以及 O_score
        O_raw：原始得分 分值范围 1-5，这个值会之后记录到数据库，作为这次测验的得分
        O_score：网页显示的得分，O_score 是 O_raw 的百分制得分，分值范围 0-100，这个数值只是在网页显示时使用，不会记录到数据库
        '''
        O_raw = self.calculateBigFiveScore(selected_survey['O_scoring'].split(','), data)
        C_raw = self.calculateBigFiveScore(selected_survey['C_scoring'].split(','), data)
        E_raw = self.calculateBigFiveScore(selected_survey['E_scoring'].split(','), data)
        A_raw = self.calculateBigFiveScore(selected_survey['A_scoring'].split(','), data)
        N_raw = self.calculateBigFiveScore(selected_survey['N_scoring'].split(','), data)

        O_norm_mean = float(selected_survey['O_norm_mean'])
        C_norm_mean = float(selected_survey['C_norm_mean'])
        E_norm_mean = float(selected_survey['E_norm_mean'])
        A_norm_mean = float(selected_survey['A_norm_mean'])
        N_norm_mean = float(selected_survey['N_norm_mean'])

        O_norm_SD = float(selected_survey['O_norm_SD'])
        C_norm_SD = float(selected_survey['C_norm_SD'])
        E_norm_SD = float(selected_survey['E_norm_SD'])
        A_norm_SD = float(selected_survey['A_norm_SD'])
        N_norm_SD = float(selected_survey['N_norm_SD'])

        O_score_detail = self.give_score_detail(O_raw, O_norm_mean, O_norm_SD, 'o', code=code)
        C_score_detail = self.give_score_detail(C_raw, C_norm_mean, C_norm_SD, 'c', code=code)
        E_score_detail = self.give_score_detail(E_raw, E_norm_mean, E_norm_SD, 'e', code=code)
        A_score_detail = self.give_score_detail(A_raw, A_norm_mean, A_norm_SD, 'a', code=code)
        N_score_detail = self.give_score_detail(N_raw, N_norm_mean, N_norm_SD, 'n', code=code)

        O_score = round(20 * (O_raw - 0), 2)  # 得分从1-5分转为百分制
        C_score = round(20 * (C_raw - 0), 2)
        E_score = round(20 * (E_raw - 0), 2)
        A_score = round(20 * (A_raw - 0), 2)
        N_score = round(20 * (N_raw - 0), 2)

        O_norm_score = round(20 * (O_norm_mean - 0), 2)  # 常模均值从1-5分转为百分制
        C_norm_score = round(20 * (C_norm_mean - 0), 2)
        E_norm_score = round(20 * (E_norm_mean - 0), 2)
        A_norm_score = round(20 * (A_norm_mean - 0), 2)
        N_norm_score = round(20 * (N_norm_mean - 0), 2)
        # 传 大五每个维度的得分及得分说明，另外还有大五每个维度大家的得分（即常模），这些数据在结果页中需要用到。
        # self.render('result.html', O_score=str(O_score), O_score_detail=O_score_detail, C_score=str(C_score), C_score_detail=C_score_detail, E_score=str(E_score), E_score_detail=E_score_detail, A_score=str(A_score), A_score_detail=A_score_detail, N_score=str(N_score), N_score_detail=N_score_detail, O_norm_score=str(O_norm_score), C_norm_score=str(C_norm_score), E_norm_score=str(E_norm_score), A_norm_score=str(A_norm_score), N_norm_score=str(N_norm_score))

        #
        # 调用模型，获取特征
        zO = (O_raw - O_norm_mean) / O_norm_SD
        zC = (C_raw - C_norm_mean) / C_norm_SD
        zE = (E_raw - E_norm_mean) / E_norm_SD
        zA = (A_raw - A_norm_mean) / A_norm_SD
        zN = (N_raw - N_norm_mean) / N_norm_SD
        params = {}
        params['O'] = zO
        params['C'] = zC
        params['E'] = zE
        params['A'] = zA
        params['N'] = zN

        # ret = qing_xu_fun(params)

        # 将答案及得分保存到数据库
        response = json.dumps(data)
        response = json.loads(response)
        # code = response['code']
        # del(response['code'])

        bigFive = {}
        bigFive['O'] = O_raw
        bigFive['C'] = C_raw
        bigFive['E'] = E_raw
        bigFive['A'] = A_raw
        bigFive['N'] = N_raw
        norm = {}
        norm['O_norm_mean'] = O_norm_mean
        norm['C_norm_mean'] = C_norm_mean
        norm['E_norm_mean'] = E_norm_mean
        norm['A_norm_mean'] = A_norm_mean
        norm['N_norm_mean'] = N_norm_mean

        response_final = {}
        response_final['code'] = code
        response_final['survey_type'] = 'standard'
        response_final['response'] = response
        response_final['bigFive'] = bigFive
        response_final['norm'] = norm
        response_final['zocean'] = params

        # 将数据保存到 responses_collection，包含问卷的 code，所有 question 的答案，加上大五各个维度的得分以及常模的五个维度的均值
        # db.responses_collection.insert(response_final)
        #
        # 写库部分进行优化，控制访问后台数据库的次数
        #
        log.info('写库response_final')
        write_queue.put(response_final)
        ret = []
        resp = {}
        resp['dim'] = 'o'
        resp['score'] = O_score
        resp['detail'] = O_score_detail
        resp['norm'] = O_norm_score
        resp['zocean'] = params
        ret.append(resp)

        resp = {}
        resp['dim'] = 'c'
        resp['score'] = C_score
        resp['detail'] = C_score_detail
        resp['norm'] = C_norm_score
        resp['zocean'] = params
        ret.append(resp)

        resp = {}
        resp['dim'] = 'e'
        resp['score'] = E_score
        resp['detail'] = E_score_detail
        resp['norm'] = E_norm_score
        resp['zocean'] = params
        ret.append(resp)

        resp = {}
        resp['dim'] = 'a'
        resp['score'] = A_score
        resp['detail'] = A_score_detail
        resp['norm'] = A_norm_score
        resp['zocean'] = params
        ret.append(resp)

        resp = {}
        resp['dim'] = 'n'
        resp['score'] = N_score
        resp['detail'] = N_score_detail
        resp['norm'] = N_norm_score
        resp['zocean'] = params
        ret.append(resp)
        return ret

    def _process_character_data(self, code, data):
        # 计算大五人格各个维度的得分
        '''
        大五得分分为 O_beta, O_raw 以及 O_score
        O_beta: 问卷算出的直接得分，大五的归一化后的 beta 系数，这个值会之后记录到数据库，作为这次测验的得分
        O_raw：O_beta 映射到 分值范围 1-5 （模型 by 徐清）
        O_score：网页显示的得分，O_score 是 O_raw 的百分制得分，分值范围 0-100，这个数值只是在网页显示时使用，不会记录到数据库
        '''
        global select_survey, db_connect, desc_map

        db = get_db_by_code(code)
        if select_survey.get(code) is None:
            selected_survey = db.surveys_collection.find_one({"code": code})
            select_survey[code] = selected_survey
        else:
            selected_survey = select_survey[code]

        O_raw, C_raw, E_raw, A_raw, N_raw = 0.0, 0.0, 0.0, 0.0, 0.0
        #
        #
        O_beta, C_beta, E_beta, A_beta, N_beta = self.calculateBigFiveScore2(code=code, data=data)

        print('O:%f C:%f E:%f A:%f N:%f' % (O_beta, C_beta, E_beta, A_beta, N_beta))

        O_norm_mean = float(selected_survey['O_norm_mean'])
        C_norm_mean = float(selected_survey['C_norm_mean'])
        E_norm_mean = float(selected_survey['E_norm_mean'])
        A_norm_mean = float(selected_survey['A_norm_mean'])
        N_norm_mean = float(selected_survey['N_norm_mean'])

        O_norm_SD = float(selected_survey['O_norm_SD'])
        C_norm_SD = float(selected_survey['C_norm_SD'])
        E_norm_SD = float(selected_survey['E_norm_SD'])
        A_norm_SD = float(selected_survey['A_norm_SD'])
        N_norm_SD = float(selected_survey['N_norm_SD'])

        O_raw = self.score(O_beta, O_norm_mean, O_norm_SD)
        C_raw = self.score(C_beta, C_norm_mean, C_norm_SD)
        E_raw = self.score(E_beta, E_norm_mean, E_norm_SD)
        A_raw = self.score(A_beta, A_norm_mean, A_norm_SD)
        N_raw = self.score(N_beta, N_norm_mean, N_norm_SD)

        O_score_detail = self.give_score_detail(O_raw, O_norm_mean, O_norm_SD, 'o', code=code, desc=desc_map)
        C_score_detail = self.give_score_detail(C_raw, C_norm_mean, C_norm_SD, 'c', code=code, desc=desc_map)
        E_score_detail = self.give_score_detail(E_raw, E_norm_mean, E_norm_SD, 'e', code=code, desc=desc_map)
        A_score_detail = self.give_score_detail(A_raw, A_norm_mean, A_norm_SD, 'a', code=code, desc=desc_map)
        N_score_detail = self.give_score_detail(N_raw, N_norm_mean, N_norm_SD, 'n', code=code, desc=desc_map)

        O_score = round(20 * (O_raw - 0), 2)  # 得分从1-5分转为百分制
        C_score = round(20 * (C_raw - 0), 2)
        E_score = round(20 * (E_raw - 0), 2)
        A_score = round(20 * (A_raw - 0), 2)
        N_score = round(20 * (N_raw - 0), 2)

        O_norm_score = round(20 * (O_norm_mean - 0), 2)  # 常模均值从1-5分转为百分制
        C_norm_score = round(20 * (C_norm_mean - 0), 2)
        E_norm_score = round(20 * (E_norm_mean - 0), 2)
        A_norm_score = round(20 * (A_norm_mean - 0), 2)
        N_norm_score = round(20 * (N_norm_mean - 0), 2)
        # 传 大五每个维度的得分及得分说明，另外还有大五每个维度大家的得分（即常模），这些数据在结果页中需要用到。
        # self.render('result.html', O_score=str(O_score), O_score_detail=O_score_detail, C_score=str(C_score), C_score_detail=C_score_detail, E_score=str(E_score), E_score_detail=E_score_detail, A_score=str(A_score), A_score_detail=A_score_detail, N_score=str(N_score), N_score_detail=N_score_detail, O_norm_score=str(O_norm_score), C_norm_score=str(C_norm_score), E_norm_score=str(E_norm_score), A_norm_score=str(A_norm_score), N_norm_score=str(N_norm_score))

        # 将答案及得分保存到数据库
        response = json.dumps(data)
        response = json.loads(response)
        # code = response['code']
        # del(response['code'])

        bigFive = {}
        bigFive['O'] = O_beta
        bigFive['C'] = C_beta
        bigFive['E'] = E_beta
        bigFive['A'] = A_beta
        bigFive['N'] = N_beta

        # TODO
        params = bigFive
        # 调用算法模型，获取特征
        #
        # ret = qingxu_func(bigFive)

        norm = {}
        norm['O_norm_mean'] = O_norm_mean
        norm['C_norm_mean'] = C_norm_mean
        norm['E_norm_mean'] = E_norm_mean
        norm['A_norm_mean'] = A_norm_mean
        norm['N_norm_mean'] = N_norm_mean

        response_final = {}
        response_final['code'] = code
        response_final['survey_type'] = 'character'
        response_final['response'] = response
        response_final['bigFive'] = bigFive
        response_final['norm'] = norm
        response_final['zocean'] = params

        # 将数据保存到 responses_collection，包含问卷的 code，所有 question 的答案，加上大五各个维度的得分以及常模的五个维度的均值
        # db.responses_collection.insert(response_final)
        #
        # 写库部分进行优化，控制访问后台数据库的次数
        #
        write_queue.put(response_final)
        ret = []
        resp = {}
        resp['dim'] = 'o'
        resp['score'] = O_score
        resp['detail'] = O_score_detail
        resp['norm'] = O_norm_score
        resp['zocean'] = params
        ret.append(resp)

        resp = {}
        resp['dim'] = 'c'
        resp['score'] = C_score
        resp['detail'] = C_score_detail
        resp['norm'] = C_norm_score
        resp['zocean'] = params
        ret.append(resp)

        resp = {}
        resp['dim'] = 'e'
        resp['score'] = E_score
        resp['detail'] = E_score_detail
        resp['norm'] = E_norm_score
        resp['zocean'] = params
        ret.append(resp)

        resp = {}
        resp['dim'] = 'a'
        resp['score'] = A_score
        resp['detail'] = A_score_detail
        resp['norm'] = A_norm_score
        resp['zocean'] = params
        ret.append(resp)

        resp = {}
        resp['dim'] = 'n'
        resp['score'] = N_score
        resp['detail'] = N_score_detail
        resp['norm'] = N_norm_score
        resp['zocean'] = params
        ret.append(resp)

        # print(ret)
        return ret


def get_question(p_dict):
    st = json.dumps(p_dict)
    q = 'http://10.200.187.131:8177/ques?user=ques&pwd=fract111&pstr=' + st
    b = requests.get(url=q).content
    return json.loads(b.decode())


#
# 单独开启线程，处理mongodb写操作
#
def write_mongo_func():
    """
    线程函数，将queue中的队列数据写入mongodb
    :return: 
    """
    #
    # 10秒的超时，超时后不管有多少条数据，都会尝试写入数据库
    log.info('write_mongo_func thread start')
    global db
    timeout = 10
    while True:
        if write_queue.empty():
            time.sleep(5)
            continue
        #
        # 累计超过10条，写库；
        # 超时，写库
        #
        if write_queue.qsize() < 50 and timeout > 0:
            time.sleep(1)
            timeout -= 1
            continue
        else:
            docs = []
            log.info('开始写操作')
            while not write_queue.empty():
                docs.append(write_queue.get())
            write_queue.task_done()
            # db.responses_model_collection.insert_many(docs)
            db.responses_collection.insert_many(docs)
            log.info('结束写操作')
            print(docs)
            time.sleep(1)
            timeout = 10
    log.info('write_mongo_func thread end')


def write_mongo_resp_model_func():
    """
    线程函数，将queue中的队列数据写入mongodb
    :return:
    """
    #
    # 10秒的超时，超时后不管有多少条数据，都会尝试写入数据库
    log.info('write_mongo_resp_model_func thread start')
    timeout = 10
    global db
    while True:
        if write_queue_response_model.empty():
            time.sleep(5)
            continue
        # 累计超过10条，写库；
        # 超时，写库
        if write_queue_response_model.qsize() < 50 and timeout > 0:
            time.sleep(1)
            timeout -= 1
            continue
        else:
            docs_response_model = []
            log.info('开始写resp_model操作')
            while not write_queue_response_model.empty():
                docs_response_model.append(write_queue_response_model.get())
            write_queue_response_model.task_done()
            # db.responses_model_collection.insert_many(docs)
            # print(docs_response_model)
            db.responses_model_collection.insert_many(docs_response_model)
            log.info('结束写操作resp_model')
            print(docs_response_model)
            time.sleep(1)
            timeout = 10
    log.info('write_mongo_resp_model_func thread end')


settings = {
    "static_path": os.path.join(os.path.dirname(__file__), "static"),
    "template_path": os.path.join(os.path.dirname(__file__), "templates"), }  # 配置静态文件路径

application = tornado.web.Application([
    (r"/survey/(.*)/(.*)", SurveyHandler),  # channel wechat
    (r"/result", ResultHandler),
    (r"/refresh", FreshHandler),
    (r"/result2", BResultHandler),
    (r"/getrmodel", GetResponseModelHandler),
    (r"/resultrmodel", BackResponseModelHandler),
    (r"/getpicurl", GetPicUrlHandler),
    (r"/getshortlongurl", GetShortlongurlHandler),
    (r"/getBXJJRshortlongurl", GetBXJJRShortlongurlHandler),
    (r"/products", ProductScore),
    (r"/wechat/(.*)/(.*)", WeChatHandler),
    (r"/frachat/(.*)/(.*)", WeChatHandlerNoJewellery),
    (r"/(.*)", SurveyListHandler), ], **settings)  # channel wechat

if __name__ == "__main__":
    # global desc_map
    #
    # 初始化时缓存好所有报告模板
    #
    db, client = us.get_db_client('survey_database')
    rows = db.surveys_collection.find()
    for row in rows:
        code = row['code']
        desc_map[code] = {}
        desc_map[code]['o'] = get_desc_by_code(code, 'o')
        desc_map[code]['c'] = get_desc_by_code(code, 'c')
        desc_map[code]['e'] = get_desc_by_code(code, 'e')
        desc_map[code]['a'] = get_desc_by_code(code, 'a')
        desc_map[code]['n'] = get_desc_by_code(code, 'n')
    args = sys.argv
    if len(args) < 2:
        port = 8090
    else:
        try:
            port = int(args[1])
        except Exception as e:
            log.error(e)
            exit(-1)
    log.info("Server start in port: %s" % (port))
    application.listen(port)
    threading.Thread(target=write_mongo_func).start()
    threading.Thread(target=write_mongo_resp_model_func).start()
    tornado.ioloop.IOLoop.instance().start()
